/*
 * Copyright (C) 2015-2019 Alibaba Group Holding Limited
 */

#include <stdio.h>
#include <stdint.h>

#include "aos/errno.h"
#include "aos/kernel.h"
#include "k_api.h"
#include "fsl_clock.h"
#include "fsl_lpuart.h"
#include "aos/hal/uart.h"
#include "board.h"
#include "hal_uart.h"
/* Then transfer byte larger than the threshold, use non-blocking method. */
#define UART_TX_INT_THRESHOLD 4

static lpuart_handle_t s_handle[9];
static ksem_t s_uartSendSem[9];
static ksem_t s_uartReceiveSem[9];
static kmutex_t s_uartRxMutex[9];
static kmutex_t s_uartTxMutex[9];
static uint8_t *uart_r_fifo[9];
static uint8_t uart_init_flag[9] = {0};

static void uart_callback(LPUART_Type *base, lpuart_handle_t *handle, status_t status, void *userData)
{
    uart_dev_t *uart = (uart_dev_t *)userData;

    if (kStatus_LPUART_TxIdle == status)
    {
        krhino_sem_give(&s_uartSendSem[uart->port]);
    }

    if (kStatus_LPUART_RxIdle == status)
    {
        krhino_sem_give(&s_uartReceiveSem[uart->port]);
    }
}

static uart_mapping_s *uart_get_port_info(uint32_t logical_port)
{
    uint32_t i = 0;
    
    if (logical_port >= PORT_UART_SIZE) {
        return NULL;
    }

    for (i = 0; i < PORT_UART_SIZE; i++) {
        if (st_uart_mapping_table[i].logical_port == logical_port) {
            return &st_uart_mapping_table[i];
        }
    }

    return NULL;
}

/**
 * Initialises a UART interface
 *
 *
 * @param[in]  uart  the interface which should be initialised
 *
 * @return  0 : on success, EIO : if an error occurred with any step
 */
int32_t hal_uart_init(uart_dev_t *uart)
{
    lpuart_config_t config = {0};
    uart_mapping_s  *pst_uart_info = NULL;
    status_t status;
    uint32_t freq = 0;

    if (NULL == uart) {
        return EIO;
    }

    if (uart_init_flag[uart->port]) {
        return EIO;
    }

    pst_uart_info = uart_get_port_info(uart->port);
    if (NULL == pst_uart_info) {
        return EIO;
    }
    
    LPUART_GetDefaultConfig(&config);
    config.baudRate_Bps = uart->config.baud_rate;
    config.enableTx = true;
    config.enableRx = true;

    switch (uart->config.parity)
    {
        case NO_PARITY:
            config.parityMode = kLPUART_ParityDisabled;
            break;
        case ODD_PARITY:
            config.parityMode = kLPUART_ParityOdd;
            break;
        case EVEN_PARITY:
            config.parityMode = kLPUART_ParityEven;
            break;
        default:
            return EIO;
    }

    switch (uart->config.data_width)
    {
#if defined(FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT) && FSL_FEATURE_LPUART_HAS_7BIT_DATA_SUPPORT
        case DATA_WIDTH_7BIT:
            config.dataBitsCount = kLPUART_SevenDataBits;
            break;
#endif
        case DATA_WIDTH_8BIT:
            config.dataBitsCount = kLPUART_EightDataBits;
            break;
        default:
            return EIO;
    }

    switch (uart->config.stop_bits)
    {
        case STOP_BITS_1:
            config.stopBitCount = kLPUART_OneStopBit;
            break;
        case STOP_BITS_2:
            config.stopBitCount = kLPUART_TwoStopBit;
            break;
        default:
            return EIO;
    }

    /* TODO: Need to have array to save clock source for all uart instances */
    
    freq = BOARD_DebugConsoleSrcFreq();
    status = LPUART_Init((LPUART_Type *)pst_uart_info->physical_port , &config, freq);
    if (kStatus_Success != status)
        return EIO;

    /* Create semaphore  TODO: Add dynamic create support*/
    if (krhino_sem_create(&s_uartSendSem[uart->port], "uartSend", 0) != RHINO_SUCCESS)
    {
        return EIO;
    }

    if (krhino_sem_create(&s_uartReceiveSem[uart->port], "uartReceive", 0) != RHINO_SUCCESS)
    {
        return EIO;
    }

    if (krhino_mutex_create(&s_uartRxMutex[uart->port], "uartRxMutex") != RHINO_SUCCESS)
    {
        return EIO;
    }

    if (krhino_mutex_create(&s_uartTxMutex[uart->port], "uartTxMutex") != RHINO_SUCCESS)
    {
        return EIO;
    }

    /* Create handle for LPUART */
    LPUART_TransferCreateHandle((LPUART_Type *)pst_uart_info->physical_port, &s_handle[uart->port],
                                uart_callback, uart);
    uart_r_fifo[uart->port] = (uint8_t *)aos_malloc(pst_uart_info->fifo_size);
    if(NULL != uart_r_fifo[uart->port]) {
        memset(uart_r_fifo[uart->port], 0, pst_uart_info->fifo_size);
        LPUART_TransferStartRingBuffer((LPUART_Type *)pst_uart_info->physical_port, &s_handle[uart->port],
                                        uart_r_fifo[uart->port], pst_uart_info->fifo_size);
    }

    uart_init_flag[uart->port] = 1;
    
    return 0;
}

/**
 * Transmit data on a UART interface
 *
 * @param[in]  uart  the UART interface
 * @param[in]  data  pointer to the start of data
 * @param[in]  size  number of bytes to transmit
 * @param[in]  timeout Systick number for timeout
 *
 * @return  0 : on success, EIO : if an error occurred with any step
 */
int32_t hal_uart_send(uart_dev_t *uart, const void *data, uint32_t size, uint32_t timeout)
{
    lpuart_transfer_t xfer;
    kstat_t stat = RHINO_SUCCESS;
    uart_mapping_s  *pst_uart_info = NULL;
    LPUART_Type *uartBase = NULL;
    lpuart_handle_t *handle = NULL;

    if (NULL == uart || data == NULL) {
        return EIO;
    }

    if (uart_init_flag[uart->port] == 0) {
        return EIO;
    }
    
    pst_uart_info = uart_get_port_info(uart->port);
    if (NULL == pst_uart_info) {
        return EIO;
    }
    
    if (timeout == 0)
    {
        timeout = 1000;
    }
    
    /* Wait for Lock */
    stat = krhino_mutex_lock(&s_uartTxMutex[uart->port], timeout);
    if (stat != RHINO_SUCCESS)
    {
        return stat;
    }

    uartBase = (LPUART_Type *)pst_uart_info->physical_port;
    handle = &s_handle[uart->port];
#if 0
    if (size < UART_TX_INT_THRESHOLD)
    {
        LPUART_WriteBlocking(uartBase, (uint8_t *)data, size);
    }
    else
#endif
    {
        xfer.data = (uint8_t *)data;
        xfer.dataSize = size;
        LPUART_TransferSendNonBlocking(uartBase, handle, &xfer);
        /* Wait for transfer finish */
        stat = krhino_sem_take(&s_uartSendSem[uart->port], timeout);

        if (stat != RHINO_SUCCESS)
        {
            LPUART_TransferAbortSend(uartBase, handle);

            /* Clear the semaphore in case it is set after timeout. */
            krhino_sem_count_set(&s_uartSendSem[uart->port], 0);

            stat = EIO;
        }
    }

    krhino_mutex_unlock(&s_uartTxMutex[uart->port]);

    return stat;
}

/**
 * Receive data on a UART interface
 *
 * @param[in]   uart         the UART interface
 * @param[out]  data         pointer to the buffer which will store incoming data
 * @param[in]   expect_size  number of bytes to receive
 * @param[out]  recv_size    number of bytes received
 * @param[in]   timeout      timeout in milisecond
 *
 * @return  0 : on success, EIO : if an error occurred with any step
 */
int32_t hal_uart_recv_II(uart_dev_t *uart, void *data, uint32_t expect_size, uint32_t *recv_size, uint32_t timeout)
{
    lpuart_transfer_t xfer;
    kstat_t stat = RHINO_SUCCESS;
    uart_mapping_s  *pst_uart_info = NULL;
    LPUART_Type *uartBase = NULL;
    lpuart_handle_t *handle = NULL;

    if (NULL == uart || data == NULL) {
        return EIO;
    }

    if (uart_init_flag[uart->port] == 0) {
        return EIO;
    }
    
    pst_uart_info = uart_get_port_info(uart->port);
    if (NULL == pst_uart_info) {
        return EIO;
    }
    
    if (timeout == 0)
    {
        timeout = 1000;
    }


    /* Wait for Lock */
    stat = krhino_mutex_lock(&s_uartRxMutex[uart->port], timeout);
    if (stat != RHINO_SUCCESS)
    {
        return stat;
    }
    
    uartBase = (LPUART_Type *)pst_uart_info->physical_port;
    handle = &s_handle[uart->port];

    xfer.data = (uint8_t *)data;
    xfer.dataSize = expect_size;
    if (kStatus_Success != LPUART_TransferReceiveNonBlocking(uartBase, handle, &xfer, recv_size))
    {
        LPUART_TransferAbortReceive(uartBase, handle);
        krhino_sem_count_set(&s_uartReceiveSem[uart->port], 0);
        krhino_mutex_unlock(&s_uartRxMutex[uart->port]);
        return EIO;
    }

    /* Wait for transfer finish */
    stat = krhino_sem_take(&s_uartReceiveSem[uart->port], timeout);

    if (stat != RHINO_SUCCESS)
    {
        /* return the data already received, count saved in *recv_size */
        if(0 == LPUART_TransferGetReceiveCount(uartBase, handle,recv_size)){
            if (*recv_size != 0) {
                stat = RHINO_SUCCESS;
            } else {
                stat = EIO;
            }
        }else{/* shouldn't go here */
            stat = EIO;
        }

        LPUART_TransferAbortReceive(uartBase, handle);
        /* Clear the semaphore in case it is set after timeout. */
        krhino_sem_count_set(&s_uartReceiveSem[uart->port], 0);

    } else
    {
        *recv_size = xfer.dataSize;
        stat = RHINO_SUCCESS;
    }

    krhino_mutex_unlock(&s_uartRxMutex[uart->port]);

    return stat;
}

/**
 * Deinitialises a UART interface
 *
 * @param[in]  uart  the interface which should be deinitialised
 *
 * @return  0 : on success, EIO : if an error occurred with any step
 */
int32_t hal_uart_finalize(uart_dev_t *uart)
{
    uart_mapping_s  *pst_uart_info = NULL;
    if (NULL == uart) {
        return EIO;
    }

    pst_uart_info = uart_get_port_info(uart->port);
    if (NULL == pst_uart_info) {
        return EIO;
    }
    
    if (uart_init_flag[uart->port] == 0) {
        return EIO;
    }
    
    LPUART_Deinit((LPUART_Type *)pst_uart_info->physical_port);
    /* Delete semaphore */
    krhino_sem_del(&s_uartSendSem[uart->port]);
    krhino_sem_del(&s_uartReceiveSem[uart->port]);
    krhino_mutex_del(&s_uartRxMutex[uart->port]);
    krhino_mutex_del(&s_uartTxMutex[uart->port]);
    if (uart_r_fifo[uart->port] != NULL) {
        aos_free(uart_r_fifo[uart->port]);
        uart_r_fifo[uart->port] = NULL;
    }
    
    return 0;
}
